/**
 * class Node
 */

package nwsim;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.lang.Math;


public class Node extends Thread{
	
	public class ReTxTimer extends TimerTask {
				
		public void run() {
			if(Constants.CompileTimeCondition.DEBUG)
				System.out.println("Node " + nodeID + " ReTx timer expired");
			this.cancel();
			reTransmit();
		}
	}
	
	public int nodeID;
	public Coordinates nodePosition; 
	public ArrayList nbrTbl;
	int destSeqNo = 0;
	int srcSeqNo = 0;
	public boolean stopThread = false;
	Timer myTimer;
	ReTxTimer myTimerTask;
	
	public Node(int id, Coordinates pos) {
		nodeID = id;
		nodePosition = pos;
		nbrTbl = new ArrayList();
		myTimer = new Timer();
		myTimerTask = new ReTxTimer();
	}
	
	public void run() {
		if(nodeID == 0) { //initiate flow
			srcSeqNo = 1;
			sendPacket(0, 1, srcSeqNo, Message.MessageType.MSG_NATIVE);
			NwSim.ETX++;
			myTimerTask.cancel();
			myTimerTask = new ReTxTimer();
			myTimer.schedule(myTimerTask, Constants.srcTimer);
			System.out.println("Node 0 injected native packet of seqNo " + srcSeqNo);
		}
		while(true) {
			Message recvdMsg;
			synchronized(NwSim.getChannel()) {
				try {
					NwSim.getChannel().wait();
				}
				catch (InterruptedException ex) {
					return;
				}
				
				recvdMsg = NwSim.getChannel().recvFromChannel();
			}
			if(recvdMsg != null) {
				for(int i=0; i<nbrTbl.size(); i++) {
					if(((Neighbor)nbrTbl.get(i)).nodeID == recvdMsg.src) { //check if src is my neighbor
						if(Math.random() <= ((Neighbor)nbrTbl.get(i)).prr) { //check delivery probability
							handlePacket(recvdMsg);
						}
						else {	//node 1 unable to decode pkt, send NACK for desired pkt no.
							if(nodeID == 1) {
								if(Constants.CompileTimeCondition.DEBUG)
									System.out.println("Node 1 sending NACK for next desired pkt, seq. no. " + (destSeqNo+1));
								sendPacket(1, 0, destSeqNo+1, Message.MessageType.MSG_NACK);
							}
						}
						break;
					}
				}
			}
			if(stopThread == true) {
				return;
			}
		}
	}
	
	void reTransmit() {
		sendPacket(nodeID, 1, srcSeqNo, Message.MessageType.MSG_NATIVE);
		NwSim.ETX++;
		System.out.println("Node " + nodeID + " retransmitting seq no. " + srcSeqNo);
		
		int delay;
		if(nodeID == 0)		delay = Constants.srcTimer;
		else 				delay = Constants.relayTimer;
		
		myTimerTask.cancel();
		myTimerTask = new ReTxTimer();
		myTimer.schedule(myTimerTask, delay);
	}
	
	void handlePacket(Message msg) {
		switch(nodeID) {
			
			case 0: {
				if(msg.msgType == Message.MessageType.MSG_ACK) {
					if(msg.msgSeqNo == srcSeqNo) { //avoid duplicate ACKs from pushing source ahead
						myTimerTask.cancel();
						if(Constants.CompileTimeCondition.DEBUG)
							System.out.println("Node 0 got ACK for pkt seq no. " + srcSeqNo);
						srcSeqNo++;
						if(srcSeqNo > Constants.dataCount) {
							System.out.println("All packets sent with *** ETX = " + NwSim.ETX + " *** Stopping simulation...");
							NwSim.stopSimulation();
						}
						else {
							sendPacket(0, 1, srcSeqNo, Message.MessageType.MSG_NATIVE);
							NwSim.ETX++;
							myTimerTask.cancel();
							myTimerTask = new ReTxTimer();
							myTimer.schedule(myTimerTask, Constants.srcTimer);
							System.out.println("Node 0 injected native packet of seqNo " + srcSeqNo);
						}
					}
				}
			}
			break;
			
			case 1: {
				if(msg.msgType == Message.MessageType.MSG_NATIVE) {
					if(msg.msgSeqNo == destSeqNo + 1) {	//if new packet, store it
						destSeqNo = msg.msgSeqNo;
						if(Constants.CompileTimeCondition.DEBUG)
							System.out.println("Node 1 received new pkt seq no. " + destSeqNo);
					}
					sendPacket(1, 0, msg.msgSeqNo, Message.MessageType.MSG_ACK); //send ACK to all received msgs
					if(Constants.CompileTimeCondition.DEBUG)
						System.out.println("Node 1 sent ACK to node 0 for seq. no. " + msg.msgSeqNo);
				}
			}
			break;
			
			case 2: {
				if(msg.msgType == Message.MessageType.MSG_ACK) {
					destSeqNo = msg.msgSeqNo; //update urself with dest's status
					myTimerTask.cancel();
					if(Constants.CompileTimeCondition.DEBUG)
						System.out.println("Node 2 overheard ACK for pkt seq. no. " + destSeqNo);
				}
				else if(msg.msgType == Message.MessageType.MSG_NACK) {
					if(Constants.CompileTimeCondition.DEBUG)
						System.out.println("Node 2 overheard NACK for pkt seqNo " + msg.msgSeqNo + ". It has pkt " + srcSeqNo);
					if(srcSeqNo == msg.msgSeqNo) { //make sure you have the packet that the dest. wants
						sendPacket(2, 1, srcSeqNo, Message.MessageType.MSG_NATIVE);
						NwSim.ETX++;
						myTimerTask.cancel();
						myTimerTask = new ReTxTimer();
						myTimer.schedule(myTimerTask, Constants.relayTimer);
						if(Constants.CompileTimeCondition.DEBUG)
							System.out.println("Node 2 got a NACK, it transmitted native pkt seq no. " + srcSeqNo);
					}
					else {
						if(Constants.CompileTimeCondition.DEBUG)
							System.out.println("Node 2 doesn't have what node 1 is NACKing, pkt seq no. " + msg.msgSeqNo);
						destSeqNo = msg.msgSeqNo - 1; //dest wants msgSeqNo, i.e. it has (msgSeqNo - 1), update urself
						myTimerTask.cancel(); //cancel any running timer
					}
				}
				else if(msg.msgType == Message.MessageType.MSG_NATIVE) {
					srcSeqNo = msg.msgSeqNo; //update urself with src's status
					if(Constants.CompileTimeCondition.DEBUG)
						System.out.println("Node 2 overheard native pkt seq no. " + srcSeqNo);
					if(msg.msgSeqNo == destSeqNo + 1) { //src is sending the pkt which dest wants
						myTimerTask.cancel(); //maybe relay was waiting for an ACK which never came.
					}
					else if(msg.msgSeqNo < destSeqNo + 1) { //Native msg is older than what the dest wants, send ACK to src
						sendPacket(2, 0, msg.msgSeqNo, Message.MessageType. MSG_ACK);
						if(Constants.CompileTimeCondition.DEBUG)
							System.out.println("Node 2 sent ACK to node 0 for seq. no. " + msg.msgSeqNo);
					}
				}
			}
			break;
			
			default: System.out.println("What the hell"); break;
		}
	}

	public void addNeighbor(int id, double prr) {
		Neighbor nbr = new Neighbor();
		nbr.nodeID = id;
		nbr.prr = prr;
		nbrTbl.add(nbr);
	}
	
	public void sendPacket(int src, int dest, int seqNo, Message.MessageType msgType) {
		Message newpkt = new Message();
		newpkt.src = src;
		newpkt.dest = dest;
		newpkt.msgSeqNo = seqNo;
		newpkt.msgType = msgType;
		NwSim.getChannel().sendToChannel(newpkt);
	}
	
}

class Neighbor {
	public int nodeID;
	public double prr;
}

